package com.hjy.backend.dataManager;

import com.hjy.backend.commons.AbstractCache;
import com.hjy.backend.dataManager.dataItem.DataItem;
import com.hjy.backend.dataManager.dataItem.DataItemImpl;
import com.hjy.backend.dataManager.logger.Logger;
import com.hjy.backend.dataManager.logger.Recover;
import com.hjy.backend.dataManager.page.Page;
import com.hjy.backend.dataManager.page.PageFirst;
import com.hjy.backend.dataManager.page.PageNormal;
import com.hjy.backend.dataManager.pageCache.PageCache;
import com.hjy.backend.dataManager.pageIndex.PageIndex;
import com.hjy.backend.dataManager.pageIndex.PageInfo;
import com.hjy.backend.transactionManager.TransactionManager;
import com.hjy.common.error.Error;
import com.hjy.backend.utils.Panic;
import com.hjy.backend.utils.Types;

public class DataManagerImpl extends AbstractCache<DataItem> implements DataManager {

    TransactionManager tm;
    PageCache pc;
    Logger logger;
    PageIndex pIndex;
    Page pageFirst; // 第一页

    public DataManagerImpl(PageCache pc, Logger logger, TransactionManager tm) {
        super(0);
        this.pc = pc;
        this.logger = logger;
        this.tm = tm;
        this.pIndex = new PageIndex();
    }

    /**
     * 为xid生成update日志
     *
     * @param xid
     * @param di
     */
    public void logDataItem(long xid, DataItem di) {
        byte[] log = Recover.updateLog(xid, di);
        logger.log(log);
    }

    public void releaseDataItem(DataItem di) {
        super.release(di.getUid());
    }

    /**
     * 在创建文件时初始化PageFirst第一页
     */
    void initPageFirst() {
        int pageId = pc.newPage(PageFirst.InitRaw());
        assert pageId == 1;
        try {
            pageFirst = pc.getPage(pageId);
        } catch (Exception e) {
            Panic.panic(e);
        }
        pc.flushPage(pageFirst);
    }

    /**
     * 在打开已有文件时时读入PageFirst，并验证正确性
     *
     * @return
     */
    boolean loadCheckPageFirst() {
        try {
            // 第一页
            pageFirst = pc.getPage(1);
        } catch (Exception e) {
            Panic.panic(e);
        }
        return PageFirst.checkVc(pageFirst);
    }

    /**
     * 初始化pageIndex
     */
    void fillPageIndex() {
        int pageNumber = pc.getPageNumber();
        // 每一个数据页页都遍历一次
        for (int i = 2; i <= pageNumber; i++) {
            Page pg = null;
            try {
                pg = pc.getPage(i);
            } catch (Exception e) {
                Panic.panic(e);
            }
            // 设置当前数据页的剩余空间
            pIndex.add(pg.getPageNumber(), PageNormal.getFreeSpace(pg));
            pg.release();
        }
    }

    /**
     * 根据 UID 获取 DataItem
     *
     * @param uid
     * @return
     * @throws Exception
     */
    @Override
    protected DataItem getForCache(long uid) throws Exception {
        // UID是8字节无符号整数 前4为是偏移, 后四位是页号
        short offset = (short) (uid & ((1L << 32) - 1));
        uid >>>= 32;
        int pageId = (int) (uid & ((1L << 32) - 1));
        Page pg = pc.getPage(pageId);
        return DataItem.parseDataItem(pg, offset, this);
    }

    /**
     * 释放缓存
     *
     * @param di
     */
    @Override
    protected void releaseForCache(DataItem di) {
        di.page().release();
    }

    /**
     * 根据UID获取一个数据项, 如果该dataItem 为不可用, 则返回null
     *
     * @param uid
     * @return
     * @throws Exception
     */
    @Override
    public DataItem read(long uid) throws Exception {
        DataItemImpl di = (DataItemImpl) super.get(uid);
        if (!di.isValid()) {
            di.release();
            return null;
        }
        return di;
    }

    @Override
    public long insert(long xid, byte[] data) throws Exception {
        // 组装成dataItem格式
        byte[] raw = DataItem.wrapDataItemRaw(data);
        if (raw.length > PageNormal.MAX_FREE_SPACE) {
            throw Error.DataTooLargeException;
        }

        PageInfo pi = null;
        for (int i = 0; i < 5; i++) {
            // 找到一个剩余空间合适的页面
            pi = pIndex.select(raw.length);
            if (pi != null) {
                break;
            } else {
                // 实在没有好的数据页, 那就再创建一个
                int newPageId = pc.newPage(PageNormal.initRaw());
                pIndex.add(newPageId, PageNormal.MAX_FREE_SPACE);
            }
        }
        if (pi == null) {
            throw Error.DatabaseBusyException;
        }

        Page page = null;
        int freeSpace = 0;
        try {
            // 获取数据页
            page = pc.getPage(pi.pageId);
            byte[] log = Recover.insertLog(xid, page, raw);
            // 添加日志
            logger.log(log);

            short offset = PageNormal.insert(page, raw);
            // 上层模块不再使用这个 page, 如果这个 page 没有人用了, 数据就会被刷新进磁盘
            page.release();
            return Types.addressToUid(pi.pageId, offset);
        } finally {
            // 将取出的page重新插入pIndex
            if (page != null) {
                pIndex.add(pi.pageId, PageNormal.getFreeSpace(page));
            } else {
                pIndex.add(pi.pageId, freeSpace);
            }
        }
    }

    @Override
    public void close() {
        super.close();
        logger.close();

        PageFirst.setVcClose(pageFirst);
        pageFirst.release();
        pc.close();
    }


}
